//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>
#include <buola/app/ceventloop.h>
#include <buola/app/ctimer.h>
#include <buola/os/cprocess.h>
#include <buola/app/cmessagequeue.h>
#include <buola/threads/thread.h>

#include <cerrno>

namespace buola {

CEventLoop::CEventLoop(const chrono::nanoseconds &pTime,bool pMainLoop)
    :   mExit(false)
    ,   mHaveWaitingFunction(false)
    ,   mIsMainLoop(pMainLoop)
    ,   mNextTimer(CMonotonicTime::max())
{
    SetLoopTime(pTime);
}

CEventLoop::~CEventLoop()
{
}

void CEventLoop::AddFunction(int pPriority,CLoopFunction *pFunction,bool pWaits)
{
    mFunctions.insert(std::pair<int,CLoopFunction*>(pPriority,pFunction));
    if(pWaits)
        mHaveWaitingFunction=true;
}

void CEventLoop::RegisterTimer(CTimer *pTimer)
{
    mTimers.push_back(pTimer);
}

void CEventLoop::UnregisterTimer(CTimer *pTimer)
{
    mTimers.erase(std::find(mTimers.begin(),mTimers.end(),pTimer));
}

void CEventLoop::UpdateTimer(CTimer *pTimer)
{
    if(pTimer->mNext<mNextTimer)
        mNextTimer=pTimer->mNext;
}

void CEventLoop::SetLoopTime(const chrono::nanoseconds &pTime)
{
    mLoopTime=std::min(pTime,chrono::nanoseconds(500000000));
}

void CEventLoop::ContinueInThread()
{
    mMutex.unlock();
    mExitThreadedLoop=false;

    thread(std::bind(&CEventLoop::ThreadedLoop,this)).detach();
}

void CEventLoop::ResumeOutsideThread()
{
    mExitThreadedLoop=true;
    mMutex.lock();
}

void CEventLoop::Loop()
{
    lock_guard<recursive_mutex> lLock(mMutex);
    while(!mExit)
    {
        Iteration();
    }
}

void CEventLoop::End()
{
    mExit=true;
}

/////////////////
// event loops
//

void CEventLoop::ThreadedLoop()
{
    lock_guard<recursive_mutex> lLock(mMutex);
    while(!mExit&&!mExitThreadedLoop)
    {
        Iteration();
    }
}

void CEventLoop::LoopUntil(bool &pCondition)
{
    while(!mExit && !pCondition)
    {
        Iteration();
    }
}

/////////////////////////////////////////////////////////
///////////////////// internal //////////////////////////
/////////////////////////////////////////////////////////

inline void CEventLoop::Iteration()
{
    CheckTimers();

    typedef std::map<int,CLoopFunction*>::iterator tIter;

    for(tIter i=mFunctions.begin();i!=mFunctions.end();++i)
    {
        if(!i->second->OnLoopFunction())
            break;
    }

    if(mIsMainLoop&&buola_pending_sigchld())
    {
        CProcess::CheckAll();
    }

    //there is no fd poller or other function that waits for the required time, so we wait for the required time here
    if(!mHaveWaitingFunction)
        sleep(CalcTimeout());
}

chrono::nanoseconds CEventLoop::CalcTimeout()
{
    if(mNextTimer==CMonotonicTime::max()) return mLoopTime;

    chrono::nanoseconds lRet=mNextTimer-CMonotonicClock::now();

    if(lRet.count()<0)
    {
        return chrono::nanoseconds(0);
    }

    return std::min(lRet,mLoopTime);
}

void CEventLoop::CheckTimers()
{
    CMonotonicTime lNow=CMonotonicClock::now();
    if(lNow<mNextTimer) return;

    bool lOneTriggered;

    do
    {
        lOneTriggered=false;
        mNextTimer=CMonotonicTime::max();

        int lNum=mTimers.size();

        for(int i=0;i<lNum;i++)
        {
            CTimer *lTimer=mTimers[i];
            if(!lTimer->mActive) continue;

            if(lNow<lTimer->mNext)  //might be next
            {
                if(lTimer->mNext<mNextTimer)
                    mNextTimer=lTimer->mNext;
                continue;
            }

            bool lDeleted=false;
            lTimer->mDeleted=&lDeleted;

            //do all this before triggering, in case it is reenabled or
            //modified in handler
            if(lTimer->mFlags&CTimer::FLAG_ONCE)
                lTimer->mActive=false;
            if(lTimer->mFlags&CTimer::FLAG_SIGNALALL)
                lTimer->mNext+=lTimer->mInterval;
            else
                lTimer->mNext=lNow+lTimer->mInterval;

            lTimer->sTimer();

            //trigger timer
            if(!lDeleted)
            {
                lTimer->mDeleted=nullptr;
            }

            lOneTriggered=true;
            //break, some timer might have been destroyed during message
            //processing, and lNum is not valid anymore
            break;
        }
    } while(lOneTriggered);
}

/*namespace buola*/ }
